package com.leyou.search.service;

import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.vo.PageResult;
import com.leyou.item.clients.ItemClient;
import com.leyou.item.dto.BrandDTO;
import com.leyou.item.dto.CategoryDTO;
import com.leyou.item.dto.SpecParamDTO;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class SearchService {

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private ItemClient itemClient;

    //基本查询条件 通过key
    private QueryBuilder buildBasicQuery(SearchRequest searchRequest){
        //查询条件
        String key= searchRequest.getKey();
        if (StringUtils.isBlank(key)){
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }

        //构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        //构建基本的match查询
        queryBuilder.must(QueryBuilders.matchQuery("all",searchRequest.getKey()).operator(Operator.AND));

        //构建过滤条件
        for (Map.Entry<String, String> stringStringEntry : searchRequest.getFilter().entrySet()) {
            String filterKey = stringStringEntry.getKey();
            //分类、品牌、规格参数的key都需要做一些处理
            if ("分类".equals(filterKey)){
                filterKey = "categoryId";
            }else if("品牌".equals(filterKey)){
                filterKey = "brandId";
            }else{
                filterKey = "specs."+ filterKey +".keyword";
            }

            //把key和value作过滤条件
            queryBuilder.filter(QueryBuilders.termQuery(filterKey,stringStringEntry.getValue()));
        }

        return queryBuilder;
    }

    /**
     * 分页查询 商品数据
     * @param searchRequest
     * @return
     */
    public PageResult<GoodsDTO> search(SearchRequest searchRequest) {

        //自定义查询
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加查询条件
        queryBuilder.withQuery(buildBasicQuery(searchRequest));

        //springData所有的分页从0页开始，现实分页从1开始，所以要-1
        queryBuilder.withPageable(PageRequest.of(searchRequest.getPage()-1,searchRequest.getSize()));

        //执行索引库的分页查询
        AggregatedPage<Goods> goodsAggregatedPage = this.esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //把查询结果封装到分页结果中三个参数，满足查询条件的总元素个数，总页数，当前页内部数据
        return new PageResult<>(goodsAggregatedPage.getTotalElements(),goodsAggregatedPage.getTotalPages(), BeanHelper.copyWithCollection(goodsAggregatedPage.getContent(),GoodsDTO.class));
    }

    /**
     * 查询过滤项 分类、品牌、规格参数
     * @param searchRequest
     * @return
     */
    public Map<String,List<?>> queryFilter(SearchRequest searchRequest) {
        //1.创建过滤项集合
        Map<String, List<?>> filterList = new LinkedHashMap<>();

        //2.查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1获取查询条件
        QueryBuilder basicQuery = buildBasicQuery(searchRequest);
        queryBuilder.withQuery(basicQuery);
        //2.2减少查询结果（这里只需要聚合结果）
        //每页显示1个
        queryBuilder.withPageable(PageRequest.of(0,1));
        //显示空的source
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        //3.聚合条件
        //3.1分类聚合
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        //3.2品牌聚合
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        //4.构建查询条件，并且自定义结果处理器
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();
        //4.1获取分类聚合
        LongTerms cTerms = aggregations.get(categoryAgg);
        List<Long> cIds = handleCategoryAgg(cTerms, filterList);
        //4.2获取品牌聚合
        LongTerms bTerms = aggregations.get(brandAgg);
        handleBrandAgg(bTerms,filterList);

        //5.动态获取规格参数聚合（需要判断是否分类，且分类个数为1）
        if(cIds != null && cIds.size() == 1){
            //获取规格参数聚合，参数：分类的id，基本查询条件，过滤项集合
            handleSpecAgg(cIds.get(0),basicQuery,filterList);
        }

        return filterList;
    }

    /**
     * 分类聚合
     * @param terms
     * @param filterList
     * @return
     */
    private List<Long> handleCategoryAgg(LongTerms terms, Map<String, List<?>> filterList) {
        //解析bucket,得到id集合
        List<Long> ids = terms.getBuckets().stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //根据id集合查询分类
        List<CategoryDTO> categoryDTOS = itemClient.queryCategoryByIds(ids);
        //存入map
        filterList.put("分类", categoryDTOS);

        return ids;
    }

    /**
     * 品牌聚合
     * @param terms
     * @param filterList
     */
    private void handleBrandAgg(LongTerms terms, Map<String, List<?>> filterList) {
        //解析bucket,得到id集合
        List<Long> ids = terms.getBuckets().stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        //根据id集合查询品牌
        List<BrandDTO> brandDTOS = itemClient.queryBrandByIds(ids);
        //存入map
        filterList.put("品牌",brandDTOS);
    }

    /**
     * 规格参数聚合
     * @param cid
     * @param basicQuery
     * @param filterList
     */
    private void handleSpecAgg(Long cid, QueryBuilder basicQuery, Map<String, List<?>> filterList) {
        //1.查询分类下需要搜索过滤的规格参数名称
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParams(null, cid, true);

        //2.查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1获取查询条件
        queryBuilder.withQuery(basicQuery);
        //2.2减少查询结果（这里只需要聚合结果）
        //每页显示1个
        queryBuilder.withPageable(PageRequest.of(0,1));
        //显示空的source
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        //3.聚合条件
        for (SpecParamDTO specParamDTO : specParamDTOS) {
            //获取param的name,作为聚合名称
            String name = specParamDTO.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs."+name+".keyword"));
        }

        //4.查询并获取结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();

        //5.解析聚合结果
        for (SpecParamDTO specParamDTO : specParamDTOS) {
            //获取param的name,作为聚合名称
            String name = specParamDTO.getName();

            StringTerms terms = aggregations.get(name);
            //获取聚合结果，注意，规格参数聚合的结果，直接是字符串，不用做特殊处理
            List<String> paramValues = terms.getBuckets()
                    .stream()
                    .map(StringTerms.Bucket::getKeyAsString)
                    //过滤空的待选项
                    .filter(StringUtils::isNoneEmpty)
                    .collect(Collectors.toList());

            //存入map
            filterList.put(name,paramValues);
        }
    }
}
